{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.101928Z",
     "start_time": "2025-01-17T12:07:51.094847Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 19
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 准备数据\n",
    "对非数据集数据进行处理，将其转换为张量。\n"
   ],
   "id": "b2ca68305d4f1003"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.191940Z",
     "start_time": "2025-01-17T12:07:51.179445Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home=\"./data\")\n",
    "\n",
    "print(type(housing))  # <class'sklearn.utils.Bunch'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.data.shape)  # (20640, 8)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.data))  # <class 'numpy.ndarray'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.target.shape)  # (20640,)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.target))  # <class 'numpy.ndarray'>"
   ],
   "id": "179320fc991c0d9e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sklearn.utils._bunch.Bunch'>\n",
      "--------------------------------------------------\n",
      "(20640, 8)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n",
      "--------------------------------------------------\n",
      "(20640,)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.210068Z",
     "start_time": "2025-01-17T12:07:51.199943Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 将数据集分为训练集和测试集\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7, test_size=0.25)\n",
    "\n",
    "# 将训练集分为训练集和验证集\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "# 把3个数据集都放到字典中\n",
    "dataset_maps = {\n",
    "    \"train\": (x_train, y_train),  # 训练集\n",
    "    \"valid\": (x_valid, y_valid),  # 验证集\n",
    "    \"test\": (x_test, y_test)  # 测试集\n",
    "}\n"
   ],
   "id": "6438d1e42b76dee3",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.248851Z",
     "start_time": "2025-01-17T12:07:51.242081Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()  #标准化\n",
    "\n",
    "# fit和fit_transform的区别，\n",
    "# fit是计算均值和方差，fit_transform是先fit，然后transform\n",
    "scaler.fit(x_train)"
   ],
   "id": "31716505cd2977d0",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-2 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-2 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-2 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-2 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-2 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-2 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-2 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" checked><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 构建数据集",
   "id": "16087ce2514248b7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.264204Z",
     "start_time": "2025-01-17T12:07:51.255857Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "# 因为最终目的是为了将数据分批，所以要继承自Dataset\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, dataset_maps, mode=\"train\"):\n",
    "        # x,y都是ndarray类型\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        # from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "        self.y = torch.from_numpy(self.y.reshape(-1, 1)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "\n",
    "    def __len__(self):  # 必须写\n",
    "        return len(self.x)  # 返回数据集的长度，0维的size\n",
    "\n",
    "    def __getitem__(self, index):  # 必须写\n",
    "        # idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[index], self.y[index]\n",
    "        # 返回第index个数据，0维的index\n",
    "\n",
    "\n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(dataset_maps, mode=\"train\")\n",
    "valid_ds = HousingDataset(dataset_maps, mode=\"valid\")\n",
    "test_ds = HousingDataset(dataset_maps, mode=\"test\")"
   ],
   "id": "6983b17b0a43ddbf",
   "outputs": [],
   "execution_count": 23
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.276035Z",
     "start_time": "2025-01-17T12:07:51.269206Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0]  # 第一个样本的x和y，都是张量",
   "id": "32eb5ab0d2d15cec",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n",
       " tensor([3.2260]))"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 24
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.313166Z",
     "start_time": "2025-01-17T12:07:51.306045Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][0]",
   "id": "5b70d99fe2daee9e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824])"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 25
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.329935Z",
     "start_time": "2025-01-17T12:07:51.324172Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][1]",
   "id": "2cac8b93a241b2cc",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([3.2260])"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 26
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## DataLoader",
   "id": "ac48caac2c0b7d1b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.366163Z",
     "start_time": "2025-01-17T12:07:51.360945Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "# 放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "# 过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "batch_size = 8  #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "e4913bc7597b3d04",
   "outputs": [],
   "execution_count": 27
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "d35e08caf2939a19"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.386614Z",
     "start_time": "2025-01-17T12:07:51.379172Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        # 全连接层\n",
    "        # Deep 部分：用于学习输入特征的高阶非线性关系。\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),  # 30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30),  # 30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # 输出层\n",
    "        # 将 Deep 部分的输出（30 维）和原始输入特征（input_dim 维）拼接后，映射到 1 维输出。\n",
    "        # Wide 部分：直接使用原始输入特征，保留低阶特征信息。\n",
    "        # 输出层：将 Deep 部分的输出和 Wide 部分的输入拼接后，通过一个全连接层生成最终输出。\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)\n",
    "\n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        # 初始化权重\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x, return_deep_output=False):\n",
    "        # 前向传播\n",
    "        deep_output = self.deep(x)\n",
    "        # print(deep_output.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        # 将两个张量（deep_output 和 x）在指定的维度（dim=1）上进行拼接（concatenation）\n",
    "        concat=torch.cat([x,deep_output], dim=1)\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        return (logits,deep_output) if return_deep_output else logits\n",
    "        # 如果return_deep_output为True，返回logits和deep_output，否则只返回logits\n"
   ],
   "id": "95800ab9c21dfe0e",
   "outputs": [],
   "execution_count": 28
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.401944Z",
     "start_time": "2025-01-17T12:07:51.397618Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 早停\n",
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -10000  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ],
   "id": "dafde4c7bc482222",
   "outputs": [],
   "execution_count": 29
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.429498Z",
     "start_time": "2025-01-17T12:07:51.423953Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 评估函数\n",
    "# 因为是回归问题，所以计算准确率无意义\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "    return np.mean(loss_list)  # # 返回验证集平均损失和准确率"
   ],
   "id": "fee145430e7ea5df",
   "outputs": [],
   "execution_count": 30
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:07:51.450616Z",
     "start_time": "2025-01-17T12:07:51.443508Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练函数\n",
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits,deep_output = model(datas,return_deep_output=True)\n",
    "                # 平均池化,尺寸为[batch size, 30]，求平均就变为[batch size],reshape成[batch size, 1]\n",
    "                deep_output = deep_output.mean(axis=1).reshape(-1,1)\n",
    "                # 尺寸一致，相加，求损失\n",
    "                logits=logits+deep_output\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ],
   "id": "3d36c3eaec6fc763",
   "outputs": [],
   "execution_count": 31
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:08:56.822731Z",
     "start_time": "2025-01-17T12:07:51.467625Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 20\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()  # 损失函数\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. 定义早停\n",
    "early_stop_callback = EarlyStopCallback(patience=10, \n",
    "                                        min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record_dict = training(model,\n",
    "                       train_loader,\n",
    "                       val_loader,\n",
    "                       epoch,\n",
    "                       loss_fct,\n",
    "                       optimizer,\n",
    "                       early_stop_callback=early_stop_callback,\n",
    "                       eval_step=len(train_loader)\n",
    "                       )"
   ],
   "id": "6fb98b8ab19337eb",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/29040 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "d51581e0456b4da3b9b2fd3790802732"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 32
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:08:56.829963Z",
     "start_time": "2025-01-17T12:08:56.823737Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"train\"][-5:]",
   "id": "acc7cd1cd573870",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.508395791053772, 'step': 29035},\n",
       " {'loss': 0.6858608722686768, 'step': 29036},\n",
       " {'loss': 0.5257912874221802, 'step': 29037},\n",
       " {'loss': 0.4023858308792114, 'step': 29038},\n",
       " {'loss': 0.055543821305036545, 'step': 29039}]"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 33
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:08:56.837148Z",
     "start_time": "2025-01-17T12:08:56.830971Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"val\"][-15:]",
   "id": "172382f4d0c2e1a8",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.5410098440383276, 'step': 7260},\n",
       " {'loss': 0.5317204902718259, 'step': 8712},\n",
       " {'loss': 0.5213400513811175, 'step': 10164},\n",
       " {'loss': 0.49485079586613645, 'step': 11616},\n",
       " {'loss': 0.4715219083403753, 'step': 13068},\n",
       " {'loss': 0.47897498582081854, 'step': 14520},\n",
       " {'loss': 0.4677047953355965, 'step': 15972},\n",
       " {'loss': 0.44672810271274577, 'step': 17424},\n",
       " {'loss': 0.4502335847740269, 'step': 18876},\n",
       " {'loss': 0.45371047210733384, 'step': 20328},\n",
       " {'loss': 0.43058424702776243, 'step': 21780},\n",
       " {'loss': 0.42597661701931566, 'step': 23232},\n",
       " {'loss': 0.4320764656001625, 'step': 24684},\n",
       " {'loss': 0.44909892248928795, 'step': 26136},\n",
       " {'loss': 0.40570857555967227, 'step': 27588}]"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 34
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:08:56.954001Z",
     "start_time": "2025-01-17T12:08:56.839156Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record_dict)  #横坐标是 steps"
   ],
   "id": "e2554260e338697e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 35
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 测试集",
   "id": "d9f6852bfa6d34fe"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:09:31.394899Z",
     "start_time": "2025-01-17T12:09:31.232013Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()  # 评估模式\n",
    "loss = evaluate(model, test_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}\")"
   ],
   "id": "506c5db54876b6ed",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.4035\n"
     ]
    }
   ],
   "execution_count": 36
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 拿到中间输出",
   "id": "f4c831eb173648c1"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:11:32.738412Z",
     "start_time": "2025-01-17T12:11:32.730794Z"
    }
   },
   "cell_type": "code",
   "source": [
    "logits,deep_output=model(train_ds[:][0].to(device),return_deep_output=True)\n",
    "deep_output.shape"
   ],
   "id": "a4bec8c1fb378c05",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([11610, 30])"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 38
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:13:01.683432Z",
     "start_time": "2025-01-17T12:13:01.642149Z"
    }
   },
   "cell_type": "code",
   "source": [
    "plt.imshow(deep_output.cpu().detach().numpy().mean(axis=0).reshape(1,-1))\n",
    "plt.yticks([])\n",
    "plt.show()"
   ],
   "id": "6bd0e00d540b3295",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 42
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
